package com.example.literj.kt.operators

import com.example.literj.kt.internal.util.ExceptionHelper
import com.example.literj.kt.internal.util.Pow2
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.atomic.AtomicReferenceArray

class SpscLinkedArrayQueue<T>() : SimplePlainQueue<T> {
    private val MAX_LOOK_AHEAD_STEP = Integer.getInteger("jctools.spsc.max.lookahead.step", 4096)
    private val producerIndex = AtomicLong()
    private var producerLookAheadStep: Int? = null
    private var producerLookAhead: Long? = null
    private var producerMask: Int? = null
    private var producerBuffer: AtomicReferenceArray<Any>? = null
    private var consumerMask: Int? = null
    private var consumerBuffer: AtomicReferenceArray<Any>? = null
    private val consumerIndex = AtomicLong()
    private val HAS_NEXT = Any()

    constructor(bufferSize: Int) : this() {
        val p2capacity: Int = Pow2.roundToPowerOfTwo(8.coerceAtLeast(bufferSize))
        val mask = p2capacity - 1
        val buffer = AtomicReferenceArray<Any>(p2capacity + 1)
        producerBuffer = buffer
        producerMask = mask
        adjustLookAheadStep(p2capacity)
        consumerBuffer = buffer
        consumerMask = mask
        producerLookAhead = (mask - 1).toLong() // we know it's all empty to start with
        soProducerIndex(0L)
    }

    override fun poll(): T? {
        val buffer = consumerBuffer!!
        val index = lpConsumerIndex()
        val mask = consumerMask!!
        val offset: Int = calcWrappedOffset(index, mask)
        val e = lvElement(buffer, offset)
        val isNextBuffer = e == HAS_NEXT
        if (null != e && !isNextBuffer) {
            soElement(buffer, offset, null)
            soConsumerIndex(index + 1)
            return e as T
        } else if (isNextBuffer) {
            return newBufferPoll(lvNextBufferAndUnlink(buffer, mask + 1), index, mask)
        }
        return null
    }

    override fun offer(e: T?): Boolean {
        if (null == e) {
            throw ExceptionHelper.createNullPointerException("Null is not a valid element")
        }
        val buffer = producerBuffer!!
        val index: Long = lpProducerIndex()
        val mask = producerMask!!
        val offset = calcWrappedOffset(index, mask)
        return if (index < producerLookAhead!!) {
            writeToQueue(buffer, e, index, offset)
        } else {
            val lookAheadStep = producerLookAheadStep!!
            val lookAheadElementOffset = calcWrappedOffset(index + lookAheadStep, mask)
            if (null == lvElement(buffer, lookAheadElementOffset)) {
                producerLookAhead = index + lookAheadStep - 1
                writeToQueue(buffer, e, index, offset)
            } else if (null == lvElement(buffer, calcWrappedOffset(index + 1, mask))) {
                writeToQueue(buffer, e, index, offset)
            } else {
                resize(buffer, index, offset, e, mask.toLong())
                true
            }
        }
    }

    override fun offer(first: T, second: T): Boolean {
        val buffer = producerBuffer!!
        val p = lvProducerIndex()
        val m = producerMask!!
        var pi: Int = calcWrappedOffset(p + 2, m)
        if (null == lvElement(buffer, pi)) {
            pi = calcWrappedOffset(p, m)
            soElement(buffer, pi + 1, second)
            soElement(buffer, pi, first)
            soProducerIndex(p + 2)
        } else {
            val capacity = buffer.length()
            val newBuffer = AtomicReferenceArray<Any>(capacity)
            producerBuffer = newBuffer
            pi = calcWrappedOffset(p, m)
            soElement(newBuffer, pi + 1, second)
            soElement(newBuffer, pi, first)
            soNext(buffer, newBuffer)
            soElement(buffer, pi, HAS_NEXT)
            soProducerIndex(p + 2)
        }
        return true
    }

    override fun isEmpty(): Boolean {
        return lvProducerIndex() == lvConsumerIndex()
    }

    override fun clear() {
        while (poll() != null || !isEmpty()) {
        }
    }

    private fun newBufferPoll(nextBuffer: AtomicReferenceArray<Any>?, index: Long, mask: Int): T? {
        consumerBuffer = nextBuffer
        val offsetInNew: Int = calcWrappedOffset(index, mask)
        val n: T? = lvElement(nextBuffer, offsetInNew) as T
        if (null != n) {
            soElement(nextBuffer, offsetInNew, null)
            soConsumerIndex(index + 1)
        }
        return n
    }

    private fun writeToQueue(
        buffer: AtomicReferenceArray<Any>,
        e: T,
        index: Long,
        offset: Int
    ): Boolean {
        soElement(buffer, offset, e)
        soProducerIndex(index + 1)
        return true
    }

    private fun resize(
        oldBuffer: AtomicReferenceArray<Any>,
        currIndex: Long,
        offset: Int,
        e: T,
        mask: Long
    ) {
        val capacity = oldBuffer.length()
        val newBuffer = AtomicReferenceArray<Any>(capacity)
        producerBuffer = newBuffer
        producerLookAhead = currIndex + mask - 1
        soElement(newBuffer, offset, e)
        soNext(oldBuffer, newBuffer)
        soElement(oldBuffer, offset, HAS_NEXT)
        soProducerIndex(currIndex + 1)
    }

    private fun soNext(curr: AtomicReferenceArray<Any>, next: AtomicReferenceArray<Any>) {
        soElement(curr, calcDirectOffset(curr.length() - 1), next)
    }

    private fun lvNextBufferAndUnlink(
        curr: AtomicReferenceArray<Any>,
        nextIndex: Int
    ): AtomicReferenceArray<Any>? {
        val nextOffset = calcDirectOffset(nextIndex)
        val nextBuffer = lvElement(curr, nextOffset) as AtomicReferenceArray<Any>
        soElement(curr, nextOffset, null)
        return nextBuffer
    }

    private fun adjustLookAheadStep(capacity: Int) {
        producerLookAheadStep = (capacity / 4).coerceAtMost(MAX_LOOK_AHEAD_STEP)
    }

    private fun lvProducerIndex(): Long {
        return producerIndex.get()
    }

    private fun lvConsumerIndex(): Long {
        return consumerIndex.get()
    }

    private fun lpProducerIndex(): Long {
        return producerIndex.get()
    }

    private fun lpConsumerIndex(): Long {
        return consumerIndex.get()
    }

    private fun soProducerIndex(v: Long) {
        producerIndex.lazySet(v)
    }

    private fun soConsumerIndex(v: Long) {
        consumerIndex.lazySet(v)
    }

    private fun calcWrappedOffset(index: Long, mask: Int): Int {
        return calcDirectOffset(index.toInt() and mask)
    }

    private fun calcDirectOffset(index: Int): Int {
        return index
    }

    private fun soElement(buffer: AtomicReferenceArray<Any>?, offset: Int, e: Any?) {
        buffer?.lazySet(offset, e)
    }

    private fun lvElement(buffer: AtomicReferenceArray<Any>?, offset: Int): Any? {
        return buffer?.get(offset)
    }
}